#include "M8266.h"
static uint32_t ticks_per_us = 180;
const uint32_t AHB_CLK_DIVDOR[] = {2, 4, 8, 16, 64, 128, 256, 512};

#define RECV_DATA_MAX_SIZE 3 * 1024 // If using large size of array, ensure system stack is large engough for the array variable。 Or stack over-bound leakage might bring about the mcu into "hardware fault exception"
#define size_file_pack 30 * 1024
u16 status = 0;
u8 link_no = 0;
// u8 RecvData[RECV_DATA_MAX_SIZE]; // (Chinese: 如果使用较大的数组，记得确保有足够大的系统堆栈来容纳这个大数组变量. 否则，单片机程序可能会因为堆栈溢出越界而跳入“hardware fault"系统异常)
uint8_t RecvData[RECV_DATA_MAX_SIZE]; // 接收缓冲,最大USART_REC_LEN个字节,起始地址为0X20001000.
u8 file_pack[size_file_pack] __attribute__((at(0X20001000)));//文件包缓存
u8 file_pack_num;//文件包编号
u32 received = 0;
u16 sent;

void led_flash(u8 times, u8 time) // 闪烁次数，间隔时间100ms
{
  for (u8 i = 0; i < times; i++)
  {
    HAL_GPIO_WritePin(GPIOI, GPIO_PIN_8, GPIO_PIN_SET);
    HAL_Delay(100 * time);
    HAL_GPIO_WritePin(GPIOI, GPIO_PIN_8, GPIO_PIN_RESET);
    HAL_Delay(100 * time);
  }
}
void LED_set(u8 led_no, u8 on)
{
  if (led_no)
  {
    if (on)
      HAL_GPIO_WritePin(GPIOI, GPIO_PIN_10, GPIO_PIN_SET);
    else
      HAL_GPIO_WritePin(GPIOI, GPIO_PIN_10, GPIO_PIN_RESET);
  }
  else
  {
    if (on)
      HAL_GPIO_WritePin(GPIOI, GPIO_PIN_8, GPIO_PIN_SET);
    else
      HAL_GPIO_WritePin(GPIOI, GPIO_PIN_8, GPIO_PIN_RESET);
  }
}
uint32_t GetTicksPerUs(void)
{
  // Systick Timer has two clock sources muxplexed. One is system core clock, the other other is "external" clock, external to Cortex. normally default to sys core clk, but not defnitely, possibly muxplex selected to extern clock input
  // (Chinese: SysTick系统滴答时钟通常有2个时钟源输入供选，1个是系统内核时钟SystemCoreClock，一种是所谓的"外部"时钟，对于Cortex核来说的"外部"输入。通常系统滴答时钟SysTick缺省会使用系统内核时钟SystemCoreClock，但不一定总是如此，也可能是外部时钟源

  // Calculate the frequency of clock source to the sys tick timer  (Chinese: 计算系统滴答的输入时钟源的频率

  uint32_t sys_tick_clk = SystemCoreClock;                   // Normally the sys_tick clock input is SystemCoreClock, but not definitely
                                                             // (Chinese:  通常，系统滴答时钟的输入是系统时钟SystemCoreClock, 但不一定总是如此
  if (((SysTick->CTRL) & (SysTick_CTRL_CLKSOURCE_Msk)) == 0) // If the source clock of SysTick is "external" Clock to Cortex.(Chinese: 如果系统通滴答时钟为cortex处理器的“外部”时钟，参见上图)
  {
#if 1
    // calculate HCLK (Chinese: 计算HCLK的频率)
    uint32_t hclk;
    uint32_t hclk_div = ((RCC->CFGR) >> 4) & 0xF;
    if ((hclk_div & 0x8) == 0)
      hclk_div = 1;
    else
      hclk_div = AHB_CLK_DIVDOR[hclk_div & 0x7];
    hclk = SystemCoreClock / hclk_div;

    sys_tick_clk = hclk / 8;      // Refer to reference manual of the MCU saying "RCC feeds the external clock of the Cortex System Timer (SysTick) with the AHB clock (HCLK) divided by 8
                                  // (Chinese: 本单片机的参考手册提到，单片机RCC给cortex提供的"外部"时钟为HCLK/8)
    if (sys_tick_clk < 1000000UL) // Since we couter SysTick Timer for timing in a precision of 1us, therefore, the Clock source to sys_tick_clk should not lowver than 1MHz. Or, ensure use SystemCoreClock
    {                             // (Chinese: 因为我们需要使用系统滴答时钟SysTick做微秒级别的延时计时，因此滴答时钟的输入频率不能低于1MHz。否则，强制使用系统内核时钟SysCoreClock做滴答时钟的时钟源。这一点也可保证，本函数的返回值不会是0）
      SysTick->CTRL |= SysTick_CTRL_CLKSOURCE_Msk;
      sys_tick_clk = SystemCoreClock;
    }

#else
    SysTick->CTRL |= SysTick_CTRL_CLKSOURCE_Msk;
#endif
  }

  return sys_tick_clk / 1000000UL;
}
void err_8266(void)
{
  led_flash(7, 1);//快闪7下
}
void reconnect_server(void)//重新连接服务器
{
	reconnect:  if (M8266WIFI_SPI_Setup_Connection(TEST_CONNECTION_TYPE, TEST_LOCAL_PORT, TEST_REMOTE_ADDR, TEST_REMOTE_PORT, link_no, 20, &status) == 0)
  {
    err_8266();
		goto reconnect;
  }
}
void check_sendSuccess(void)//检查是否发送成功
{
	if( (status&0xFF) == 0x00 )
	{
	}
	else
	{
		 if( ((status&0xFF) == 0x14 )                     // 0x14 = connection of link_no not present (Chinese: 该套接字不存在)
			|| ((status&0xFF) == 0x15) )                    // 0x15 = connection of link_no closed(Chinese: 该套接字已经关闭或断开)
		 {
					M8266HostIf_delay_us(99);
				 reconnect_server();//need to re-establish the socket connection (Chinese: 需要重建建立套接字)
		 }
		 else if( (status&0xFF) == 0x18 )                 // 0x18 = TCP server in listening states and no tcp clients have connected. (Chinese: 这个TCP服务器还没有客户端连接着它)
		 {
				 M8266HostIf_delay_us(100);
		 }
		 else
		 {
				 M8266HostIf_delay_us(250);
		 }
	}
}
// delay_Init() mainly ensure system tick is enabled and calculate ticks_per_us. Please call it after System Clock configuratuon.
// (Chinese: delay_Init()这个函数主要是确保系统滴答时钟在运行，并正确地计算出参数ticks_per_us每微秒对应多少个ticks. 请在系统时钟配置之后调用它)
void delay_Init(void)
{
  ticks_per_us = GetTicksPerUs();

  if (((SysTick->CTRL) & (SysTick_CTRL_ENABLE_Msk)) == 0) // If system tick is not enabled, then enable it since we will use it for delay_us()
  {                                                       // (Chinese: 如果系统滴答时钟尚未开启，我们就开启它，因为我们的微秒延时函数delay_us()会使用系统滴答时钟来计时)
    if (SysTick->LOAD == 0)
    {
      SysTick->LOAD = (uint32_t)(ticks_per_us * 1000U - 1UL); // config System tick to reload each 1ms (Chinese: 配置系统滴答时钟每毫秒重新加载一次，毕竟总得给它一个合法有效的数值)
    }

    SysTick->CTRL |= SysTick_CTRL_ENABLE_Msk; // Enable System tick  (Chinese: 启动系统滴答时钟)
  }
}
// The delay_us() just "query" the free-running system ticks. It will not modify any regisyters of system tick timer, therefore NOT INFLUENCE OTHER PARTS the using system tick timer
// (Chinese: 这个微秒延时函数delay_us的写法，仅仅只是"查询"系统滴答时钟的计数，它并不会修改其寄存器，因此不会影响系统其他部分使用系统滴答时钟。运行中其他部分也不要随便修改滴答时钟的配置)
uint32_t delta;
void delay_us(uint8_t nus)
{

  /*
     In a multiple-task system, please add function of lock schedule at beginning to aviod influence to the delay accuracy
     (Chinese: 在多任务系统里，请在这个函数实现的一开始处加上临时关闭任务调度，避免任务调度对延时精度的影响)
  */

  uint32_t start, now, reload;
  start = SysTick->VAL;
  reload = SysTick->LOAD;
  do
  {
    now = SysTick->VAL;
    delta = start >= now ? start - now : reload + start - now;
  } while (delta < ticks_per_us * nus);

  /*
   In a multiple-task system, remember to unlock schedule here before return from the function
   (Chinese: 在多任务系统里，在退出这个函数前，记得要重新恢复任务调度)
*/
}

/***********************************************************************************
 * M8266WIFIHostIf_delay_us                                                        *
 * Description                                                                     *
 *    To loop delay some micro seconds.                                            *
 * Parameter(s):                                                                   *
 *    1. nus: the micro seconds to delay                                           *
 * Return:                                                                         *
 *    none                                                                         *
 ***********************************************************************************/
void M8266HostIf_delay_us(u8 nus)
{
  //	vTaskSuspendAll();
  //  delay_us(nus);
  HAL_Delay(1);
  //	xTaskResumeAll();
}
/***********************************************************************************
 * M8266HostIf_GPIO_SPInCS_nRESET_Pin_Init                                         *
 * Description                                                                     *
 *    To initialise the GPIOs for SPI nCS and nRESET output for M8266WIFI module   *
 *    You may update the macros of GPIO PINs usages for nRESET from brd_cfg.h      *
 *    You are not recommended to modify codes below please                         *
 * Parameter(s):                                                                   *
 *    None                                                                         *
 * Return:                                                                         *
 *    None                                                                         *
 ***********************************************************************************/
void M8266HostIf_GPIO_CS_RESET_Init(void)
{
  GPIO_InitTypeDef GPIO_InitStructure;

  // Initial STM32's GPIO for M8266WIFI_SPI_nCS
  M8266WIFI_SPI_nCS_GPIO_RCC_CLOCK_EN;                             // enable nCS GPIO clock
  GPIO_OUTPUT_HIGH(M8266WIFI_SPI_nCS_GPIO, M8266WIFI_SPI_nCS_PIN); // nCS output high initially
  GPIO_InitStructure_AS_GPIO_OUTPUT(M8266WIFI_SPI_nCS_GPIO, M8266WIFI_SPI_nCS_PIN);

  // Initial STM32's GPIO for M8266WIFI_nRESET
  M8266WIFI_nRESET_GPIO_RCC_CLOCK_EN;                            // enable nRESET GPIO clock
  GPIO_OUTPUT_HIGH(M8266WIFI_nRESET_GPIO, M8266WIFI_nRESET_PIN); // nRESET output high initially
  GPIO_InitStructure_AS_GPIO_OUTPUT(M8266WIFI_nRESET_GPIO, M8266WIFI_nRESET_PIN);
}
/***********************************************************************************
 * M8266HostIf_SPI_Init                                                            *
 * Description                                                                     *
 *    To initialise the SPI Interface for M8266WIFI module                         *
 *    You may update the macros of SPI usages for nRESET from brd_cfg.h            *
 *    You are not recommended to modify codes below please                         *
 * Parameter(s):                                                                   *
 *    None                                                                         *
 * Return:                                                                         *
 *    None                                                                         *
 ***********************************************************************************/
#if defined(MCU_IS_STM32F4XX_HAL) || defined(MCU_IS_STM32F7XX) || defined(MCU_IS_STM32L1XX) || defined(MCU_IS_STM32L4XX) || defined(MCU_IS_STM32H7XX)
SPI_HandleTypeDef hspi;
#endif
void M8266HostIf_SPI_Init(void)
{
  GPIO_InitTypeDef GPIO_InitStructure;
  __HAL_RCC_GPIOB_CLK_ENABLE(); // Enable the GPIOB Clock
  GPIO_InitStructure.Pin = GPIO_PIN_3 | GPIO_PIN_4 | GPIO_PIN_5;
  GPIO_InitStructure.Mode = GPIO_MODE_AF_PP;
  GPIO_InitStructure.Speed = GPIO_SPEED_HIGH;
  GPIO_InitStructure.Pull = GPIO_PULLUP;
  GPIO_InitStructure.Alternate = GPIO_AF5_SPI1;
  HAL_GPIO_Init(GPIOB, &GPIO_InitStructure);
  __HAL_RCC_SPI1_CLK_ENABLE();
  hspi.Instance = SPI1;

  hspi.Init.Mode = SPI_MODE_MASTER;
  hspi.Init.Direction = SPI_DIRECTION_2LINES;
  hspi.Init.DataSize = SPI_DATASIZE_8BIT;
  hspi.Init.CLKPolarity = SPI_POLARITY_LOW;
  hspi.Init.CLKPhase = SPI_PHASE_1EDGE;
  hspi.Init.NSS = SPI_NSS_SOFT;
  hspi.Init.BaudRatePrescaler = SPI_BAUDRATEPRESCALER_8;
  hspi.Init.FirstBit = SPI_FIRSTBIT_MSB;
  hspi.Init.TIMode = SPI_TIMODE_DISABLE;
  hspi.Init.CRCCalculation = SPI_CRCCALCULATION_DISABLE;
  hspi.Init.CRCPolynomial = 7;

  if (HAL_SPI_Init(&hspi) != HAL_OK)
  {
    while (1)
      ;
  }
  __HAL_SPI_ENABLE(&hspi);
}
/***********************************************************************************
 * M8266HostIf_Init                                                                *
 * Description                                                                     *
 *    To initialise the Host interface for M8266WIFI module                        *
 * Parameter(s):                                                                   *
 *    baud: baud rate to set                                                       *
 * Return:                                                                         *
 *    None                                                                         *
 ***********************************************************************************/
void M8266HostIf_Init(void)
{
  M8266HostIf_GPIO_CS_RESET_Init();

  M8266HostIf_SPI_Init();
  //	 M8266HostIf_SPI_SetSpeed(SPI_BaudRatePrescaler_8);
}
/***********************************************************************************
 * M8266HostIf_SPI_SetSpeed                                                        *
 * Description                                                                     *
 *    To setup the SPI Clock Speed for M8266WIFI module                            *
 * Parameter(s):                                                                   *
 *    SPI_BaudRatePrescaler: SPI BaudRate Prescaler                                *
 * Return:                                                                         *
 *    None                                                                         *
 ***********************************************************************************/
void M8266HostIf_SPI_SetSpeed(u32 SPI_BaudRatePrescaler)
{
#if defined(MCU_IS_STM32F1XX) || defined(MCU_IS_STM32F3XX) || defined(MCU_IS_STM32F2XX) || defined(MCU_IS_STM32F4XX)
  assert_param(IS_SPI_BAUDRATE_PRESCALER(SPI_BaudRatePrescaler));
  M8266WIFI_INTERFACE_SPI->CR1 &= 0XFFC7;
  M8266WIFI_INTERFACE_SPI->CR1 |= SPI_BaudRatePrescaler;
  SPI_Cmd(M8266WIFI_INTERFACE_SPI, ENABLE);
#elif defined(MCU_IS_STM32F4XX_HAL) || defined(MCU_IS_STM32F7XX) || defined(MCU_IS_STM32L1XX) || defined(MCU_IS_STM32L4XX)
  __HAL_SPI_DISABLE(&hspi);
  hspi.Instance->CR1 &= 0XFFC7;
  hspi.Instance->CR1 |= SPI_BaudRatePrescaler;
  __HAL_SPI_ENABLE(&hspi);
#elif defined(MCU_IS_STM32H7XX)
  __HAL_SPI_DISABLE(&hspi);
  hspi.Instance->CFG1 &= ~(7UL << 28);
  hspi.Instance->CFG1 |= SPI_BaudRatePrescaler;
  __HAL_SPI_ENABLE(&hspi);
#else
#error YOU SHOULD DEFINED MCU_IS_STM32F?XX in brd_cfg.h
#endif
}
/***********************************************************************************
 * M8266HostIf_Set_SPI_nCS_PIN                                                     *
 * Description                                                                     *
 *    To Outpout HIGH or LOW onto the GPIO pin for M8266WIFI SPI nCS               *
 *    You may update the macros of GPIO PIN usages for SPI nCS from brd_cfg.h      *
 *    You are not recommended to modify codes below please                         *
 * Parameter(s):                                                                   *
 *    1. level: LEVEL output to SPI nCS pin                                        *
 *              0 = output LOW  onto SPI nCS                                       *
 *              1 = output HIGH onto SPI nCS                                       *
 * Return:                                                                         *
 *    None                                                                         *
 ***********************************************************************************/
void M8266HostIf_Set_SPI_nCS_Pin(u8 level)
{
  if (level != 0)
    M8266WIFI_SPI_nCS_GPIO->BSRR = M8266WIFI_SPI_nCS_PIN;
  else
    M8266WIFI_SPI_nCS_GPIO->BSRR = M8266WIFI_SPI_nCS_PIN << 16;
}
/***********************************************************************************
 * M8266HostIf_Set_nRESET_Pin                                                      *
 * Description                                                                     *
 *    To Outpout HIGH or LOW onto the GPIO pin for M8266WIFI nRESET                *
 *    You may update the macros of GPIO PIN usages for nRESET from brd_cfg.h       *
 *    You are not recommended to modify codes below please                         *
 * Parameter(s):                                                                   *
 *    1. level: LEVEL output to nRESET pin                                         *
 *              0 = output LOW  onto nRESET                                        *
 *              1 = output HIGH onto nRESET                                        *
 * Return:                                                                         *
 *    None                                                                         *
 ***********************************************************************************/
void M8266HostIf_Set_nRESET_Pin(u8 level)
{
  if (level != 0)
    M8266WIFI_nRESET_GPIO->BSRR = M8266WIFI_nRESET_PIN;
  else
    M8266WIFI_nRESET_GPIO->BSRR = M8266WIFI_nRESET_PIN << 16;
}
/***********************************************************************************
 * M8266WIFI_Module_Hardware_Reset                                                 *
 * Description                                                                     *
 *    1. To perform a hardware reset to M8266WIFI module via the nReset Pin        *
 *       and bring M8266WIFI module to boot up from external SPI flash             *
 *    2. In order to make sure the M8266WIFI module bootup from external           *
 *       SPI flash, nCS should be low during Reset out via nRESET pin              *
 * Parameter(s):                                                                   *
 *    none                                                                         *
 * Return:                                                                         *
 *    none                                                                         *
 ***********************************************************************************/
void M8266WIFI_Module_Hardware_Reset(void) // total 800ms  (Chinese: 本例子中这个函数的总共执行时间大约800毫秒)
{
  M8266HostIf_Set_SPI_nCS_Pin(0); // Module nCS==ESP8266 GPIO15 as well, Low during reset in order for a normal reset (Chinese: 为了实现正常复位，模块的片选信号nCS在复位期间需要保持拉低)
  HAL_Delay(1);                   // delay 1ms, adequate for nCS stable (Chinese: 延迟1毫秒，确保片选nCS设置后有足够的时间来稳定)

  M8266HostIf_Set_nRESET_Pin(0); // Pull low the nReset Pin to bring the module into reset state (Chinese: 拉低nReset管脚让模组进入复位状态)
  HAL_Delay(500);                  // delay 5ms, adequate for nRESET stable(Chinese: 延迟5毫秒，确保片选nRESER设置后有足够的时间来稳定，也确保nCS和nRESET有足够的时间同时处于低电平状态)
                                 // give more time especially for some board not good enough
                                 //(Chinese: 如果主板硬件不是很好，导致上升下降过渡时间较长，或者因为失配存在较长的振荡时间，所以信号到轨稳定的时间较长，那么在这里可以多给一些延时)

  M8266HostIf_Set_nRESET_Pin(1);  // Pull high again the nReset Pin to bring the module exiting reset state (Chinese: 拉高nReset管脚让模组退出复位状态)
  HAL_Delay(2000);                 // at least 18ms required for reset-out-boot sampling boottrap pin (Chinese: 至少需要18ms的延时来确保退出复位时足够的boottrap管脚采样时间)
                                  // Here, we use 300ms for adequate abundance, since some board GPIO, (Chinese: 在这里我们使用了300ms的延时来确保足够的富裕量，这是因为在某些主板上，)
                                  // needs more time for stable(especially for nRESET) (Chinese: 他们的GPIO可能需要较多的时间来输出稳定，特别是对于nRESET所对应的GPIO输出)
                                  // You may shorten the time or give more time here according your board v.s. effiency
                                  // (Chinese: 如果你的主机板在这里足够好，你可以缩短这里的延时来缩短复位周期；反之则需要加长这里的延时。
                                  //           总之，你可以调整这里的时间在你们的主机板上充分测试，找到一个合适的延时，确保每次复位都能成功。并适当保持一些富裕量，来兼容批量化时主板的个体性差异)
  M8266HostIf_Set_SPI_nCS_Pin(1); // release/pull-high(defualt) nCS upon reset completed (Chinese: 释放/拉高(缺省)片选信号
                                  // HAL_Delay(1);         // delay 1ms, adequate for nCS stable (Chinese: 延迟1毫秒，确保片选nCS设置后有足够的时间来稳定)

  HAL_Delay(800 - 300 - 5 - 2); // Delay more than around 500ms for M8266WIFI module bootup and initialization，including bootup information print。No influence to host interface communication. Could be shorten upon necessary. But test for verification required if adjusted.
                                // (Chinese: 延迟大约500毫秒，来等待模组成功复位后完成自己的启动过程和自身初始化，包括串口信息打印。但是此时不影响模组和单片主机之间的通信，这里的时间可以根据需要适当调整.如果调整缩短了这里的时间，建议充分测试，以确保系统(时序关系上的)可靠性)
}
/***********************************************************************************
 * M8266WIFI_SPI_wait_sta_connecting_to_ap_and_get_ip                              *
 * Description                                                                     *
 *    Wait M8266WIFI in STA mode connecting to AP/Router and get ip address        *
 *    via SPI API functions in M8266WIFIDrv.lib                                    *
 * Parameter(s):                                                                   *
 *    1. sta_ip: if success, sta_ip return the ip address achieved                 *
 *    2. max_wait_time_in_s: max timeout to wait in seconds                        *
 * Return:                                                                         *
 *       1 = M8266WIFI module connects AP/router and gets a valid IP               *
 *           from AP/router before the timeout succefully                          *
 *       0 = M8266WIFI module fails to connect the AP/router and get               *
 *           a valid IP before the timeout                                         *
 ***********************************************************************************/
u8 M8266WIFI_SPI_wait_sta_connecting_to_ap_and_get_ip(char *sta_ip, u8 max_wait_time_in_s)
{
  u16 i;
  u16 status = 0;

  for (i = 0; i < 1 * max_wait_time_in_s; i++) // max wait
  {
    if ((M8266WIFI_SPI_Get_STA_IP_Addr(sta_ip, &status) == 1) && (strcmp(sta_ip, "0.0.0.0") != 0))
      break;

    HAL_Delay(1000);
    continue;
  }

  if (i >= 1 * max_wait_time_in_s)
    return 0; // false

  return 1; // true
}
/***********************************************************************************
 * M8266WIFI_Module_Init_Via_SPI(void)                                             *
 * Description                                                                     *
 *    To perform a Initialization sequency to M8266WIFI module via SPI I/F         *
 *    (1) Reboot the M8266WIFI module via nRESET pin                               *
 *    (2) Wait and Check if the M8266WIFI module gets an valid IP address          *
 *        if the module in STA or STA+AP mode                                      *
 *    Call this function after Host SPI Interface Initialised                      *
 *    if use SPI interface to config module                                        *
 * Parameter(s):                                                                   *
 *    none                                                                         *
 * Return:                                                                         *
 * Return:                                                                         *
 *       0 = failed                                                                *
 *       1 = success                                                               *
 ***********************************************************************************/
u8 M8266WIFI_Module_Init_Via_SPI(void)
{
  u32 spi_clk = 1000000;
  //  u8 sta_ap_mode = 3;
  //  u8 connection_status = 0xFF;
  //  char sta_ip[15 + 1] = {0};
  //  char ssid[32];
  //  s8 rssi;
  u16 status = 0;

  // 步骤1：对模组执行硬复位时序(在片选nCS拉低的时候对nRESET管脚输出低高电平)，并等待模组复位启动完毕
  //  Step 1: To hardware reset the module (with nCS=0 during reset) and wait up the module bootup
  M8266WIFI_Module_Hardware_Reset();
  led_flash(1, 1);
// 步骤2：在确保SPI底层通信可靠的前提下，调整SPI时钟尽可能的快，以免支持最快速度通信。本模组最大可以支持40MHz的SPI频率
//  Step 2: Try SPI clock in a fast one as possible up to 40MHz (M8266WIFI could support only upto 40MHz SPI)
#if (M8266WIFI_SPI_INTERFACE_NO == 1) || (M8266WIFI_SPI_INTERFACE_NO == 4)
  M8266HostIf_SPI_SetSpeed(SPI_BaudRatePrescaler_4); // Setup SPI Clock. Here 84/4 = 21.0MHz for STM32F4xx SPI1 or SPI4, up to 40MHz, since SPI1/4 clock devided from faster APB2 clock
#elif (M8266WIFI_SPI_INTERFACE_NO == 2) || (M8266WIFI_SPI_INTERFACE_NO == 3)
  M8266HostIf_SPI_SetSpeed(SPI_BaudRatePrescaler_2); // Setup SPI Clock. Here 42/2 = 21.0MHz for STM32F4xx SPI2 or SPI3, up to 21MHz, since SPI2/3 clock devided from lowver APB1 clock
#endif
  spi_clk = 21000000;
  HAL_Delay(1); // wait clock stable (Chinese: 设置SPI时钟后，延时等待时钟稳定)

  // 步骤3：调用M8266HostIf_SPI_Select()。 在正式调用驱动API函数和模组进行通信之前，调用M8266HostIf_SPI_Select()来告诉驱动使用哪个SPI以及SPI的时钟有多快，这一点非常重要。
  //                   如果没有调用这个API，单片机主机和模组之间将可能将无法通信
  //  Step 3: It is very mandatory to call M8266HostIf_SPI_Select() to tell the driver which SPI you used and how faster the SPI clock you used. The function must be called before SPI access
  if (M8266HostIf_SPI_Select((uint32_t)M8266WIFI_INTERFACE_SPI, spi_clk, &status) == 0)
  {
    // If M8266HostIf_SPI_Select() fails here, then check your host interface wiring and initialization
    // (Chinese: 如果你在执行时 M8266HostIf_SPI_Select()失败而进入了这里，请仔细检查主机接口的接线是否正确和可靠，SPI主机接口初始化是否正确。
    //           可参考《ALK8266WIFI模组SPI接口高速通信使用与集成_主机集成说明》之章节“底层调试技巧--主机接口的硬件接线、初始化和匹配以及验证技巧（选阅）” 进行快速梳理和定位。
    err_8266();
  }
  led_flash(3, 3);
  // 步骤4：开发阶段和测试阶段，用于测试评估主机板在当前频率下进行高速SPI读写访问时的可靠性。
  //          如果足够可靠，则可以适当提高SPI频率；如果不可靠，则可能需要检查主机板连线或者降低SPI频率。
  //          产品研发完毕进入正式产品化发布阶段后，因为在研发阶段已经确立了最佳稳定频率，建议这里改成 #if 0，不必再测试
  // Step 4: Used to evaluate the high-speed spi communication. Changed to #if 0 to comment it for formal release
#if 0
  {
    volatile u32 i, j;
    u8 byte;

    if (M8266WIFI_SPI_Interface_Communication_OK(&byte) == 0) //	if SPI logical Communication failed
    {
      while (1)
      {
        // If M8266HostIf_SPI_Select() fails here, then check your host interface wiring and initialization
        // (Chinese: 如果你在执行时 M8266HostIf_SPI_Select()失败而进入了这里，请仔细检查主机接口的接线是否正确和可靠，SPI主机接口初始化是否正确。
        //           可参考《ALK8266WIFI模组SPI接口高速通信使用与集成_主机集成说明》之章节“底层调试技巧--主机接口的硬件接线、初始化和匹配以及验证技巧（选阅）” 进行快速梳理和定位。
#ifdef USE_LED_AND_KEY_FOR_TEST // MB LEDs flash in 1Hz uppon errors
        LED_set(0, 0);
        LED_set(1, 0);
        HAL_Delay(500);
        LED_set(0, 1);
        LED_set(1, 1);
        HAL_Delay(500);
#endif
      }
    }

    i = 100000;
    j = M8266WIFI_SPI_Interface_Communication_Stress_Test(i);
    if ((j < i) && (i - j > 5)) //  if SPI Communication stress test failed (Chinese: SPI底层通信压力测试失败，表明你的主机板或接线支持不了当前这么高的SPI频率设置)
    {
      while (1)
      {
#ifdef USE_LED_AND_KEY_FOR_TEST // MB LEDs flash in 1Hz uppon errors
        LED_set(0, 0);
        LED_set(1, 0);
        HAL_Delay(500);
        LED_set(0, 1);
        LED_set(1, 1);
        HAL_Delay(500);
#endif
      }
    }
  }
#endif

  // 步骤5：配置模组
  // Step 5: Conifiguration to module
  // (Chinese:第5步：)

#if 0 // Chinese: 5.1 如果你希望减小模组的最大发射功率，可以将这里改成 #if 1，并调整下面的 tx_max_power参数的值
      // 5.1 If you hope to reduce the Max Tx power, you could enable it by change to "#if 1"
      // u8 M8266WIFI_SPI_Set_Tx_Max_Power(u8 tx_max_power, u16 *status)
        if(M8266WIFI_SPI_Set_Tx_Max_Power(68, &status)==0)   // tx_max_power=68 to set the max tx power of aroud half of manufacture default, i.e. 50mW or 17dBm. Refer to the API specification for more info
          return 0;                                          // (Chinese: tx_max_power=68表示将发射最大功率设置为出厂缺省数值的一般，即50mW或者17dBm。具体数值含义可以查看这个API函数的头文件声明里的注释
#endif

#if 0 // Chinese: 5.2 如果你希望配置或关闭模组上自带的WEB服务器，你可以将这里改成 #if 1，并调整下面的API函数里的相关参数值
      // 5.2 If you expect to config or stop the on-module web server, you could enable it by change to "#if 1" and adjust as required
      // u8 M8266WIFI_SPI_Set_WebServer(u8 open_not_shutdown, u16 server_port, u8 saved, u16* status)
         if(M8266WIFI_SPI_Set_WebServer(0, 3128, 0, &status)==0)  // 0 -> to shutdown the web_server (Chinese: 第一个参数0表示关闭WEB服务器)
         {
              return 0;
         }
       //u8 M8266WIFI_SPI_Set_WebServer(u8 open_not_shutdown, u16 server_port, u8 saved, u16* status)
         if(M8266WIFI_SPI_Set_WebServer(1, 3128, 0, &status)==0)  // 1-> to (re-)start the web_server with port = 3128, not saved, invalid after reboot
         {                                                        // (Chinese: 第一个参数1表示开启WEB服务器，第二个参数3128表示将WEB服务器的端口改成3128，第三个参数0表示不保存，只是当前设置有效，模组复位后失效)
              return 0;
         }
#endif

#if 0 // Chinese: 5.3 如果你希望改变模组的op_mode，不使用模组启动时缺省op_mode，你可以这里改成 #if 1，并调整下面的API函数里的相关参数值
      // 5.3 If you expect to change the op_mode overrding the default loaded from flash on bootup, enable it by "#if 1"
      //  u8 M8266WIFI_SPI_Set_Opmode(u8 op_mode, u8 saved, u16* status);
      // set to AP Only mode, not saved // 1=STA Only, 2=AP Only, 3=STA+AP
      //(Chinese: 设置为AP Only模式。1=STA Only, 2=AP Only, 3=STA+AP)
  if (M8266WIFI_SPI_Set_Opmode(3, 0, &status) == 0)
  {
    return 0;
  }
#endif

#if 0 // Chinese: 5.4 查询当前的op_mode，如果处于STA模式或者STA+AP模式，那么可根据需要执行配网去链接上第三方热点/路由器，并等待获取ip地址
  //                                 如果处于AP模式或者STA+AP模式， 那么可根据需要设置AP参数，或者执行选择更加的AP频道
  // 5.4 get current op_mode, if STA mode or STA+AP mode, then, wait connection to external AP and get ip
  //                          if AP mode or STA+AP mode, then config ap parameters and optimized to select the AP channel unpon necessary

  // u8 M8266WIFI_SPI_Get_Opmode(u8* op_mode, u16* status);
  if (M8266WIFI_SPI_Get_Opmode(&sta_ap_mode, &status) == 0)
    return 0;

  if ((sta_ap_mode == 2)     // if AP mode (Chinese: 如果是AP模式
      || (sta_ap_mode == 3)) // if STA+AP mode(Chinese: 如果是STA+AP模式)
  {
#if 0 // If you expect to change the ap info overrding the default loaded from flash on bootup, enable it by "#if 1"
      // (Chinese: 如果你希望改变模组作为AP热点时AP热点名称和密码，不使用模组启动时缺省参数，你可以这里改成 #if 1，并调整下面的API函数里的相关参数值)
      // u8 M8266WIFI_SPI_Config_AP(u8 ssid[27+1], u8 password[27+1], u8 enc, u8 channel, u8 saved, u16* status);
            if(M8266WIFI_SPI_Config_AP((u8*)"Anylinkin", (u8*)"1234567890", 4, 1, 0, &status)==0)  // set to 4=WPA_WPA2_PSK, not saved // 0=OPEN, 1=WEP, 2=WPA_PSK, 3=WPA2_PSK, 4=WPA_WPA2_PSK
                 return 0;
#endif

#if 0 // If you expect to select the AP channel optimized accordingto the wifi enviroment, enable it by "#if 1"
      // (Chinese: 如果你希望根据周围的无线环境优选AP的频道，你可以这里改成 #if 1)
      // u8 M8266WIFI_SPI_OptSel_Local_Ap_Channel(u8* opt_channel, u8 saved, u16* status)
    if (M8266WIFI_SPI_OptSel_Local_Ap_Channel(NULL, 0, &status) == 0)
      return 0;
#endif
  } // end to if(  (sta_ap_mode == 2) ....

  if ((sta_ap_mode == 1)     // if STA mode (Chinese: 如果是STA模式
      || (sta_ap_mode == 3)) // if STA+AP mode(Chinese: 如果是STA+AP模式)
  {

#if 0 // If you expect to use smartconfig to config the module, enable here by "#if 1" and prepare to send ssid using your smart devices such as smartphone
      // (Chinese: 如果你希望使用智能配网来进行配网，你可以这里改成 #if 1)
            u8 smartconfig_type;
            char smartconfig_phone_ip[15+1]={0};
         // u8 M8266WIFI_SPI_DoModuleSmartConfig(u8 timeout_in_s, u8 saved, u8* smartconfig_type, char smartconfig_phone_ip[15+1], u16* status);
            if(M8266WIFI_SPI_DoModuleSmartConfig(30, 0, &smartconfig_type, smartconfig_phone_ip, &status)==0)
                 return 0;
				 
            while(1){}
#endif

#if 0 // If you expect to use the SSID and password here overriding the default loaded from on-module SPI flash, enable it by "#if 1", and update the ssid and passowrd with those of your routers connected to.
      // (Chinese: 如果模组上保存有之前连接过的热点/路由器的SSID和密码（称为缺省SSID和密码），而模组启动后的模组包含STA，那么模组会自动加载这个缺省的SSID和密码自动去连接路由器和热点。
      //           但是如果你不希望使用这个缺省的SSID和密码来连接路由器/热点，你可以这里改成 #if 1，并将其中的SSID和密码改成你所期望连接的热点/路由器的)
      // u8 M8266WIFI_SPI_STA_Connect_Ap(u8 ssid[32], u8 password[64], u8 saved, u8 timeout_in_s, u16* status);
    if (M8266WIFI_SPI_STA_Connect_Ap((u8 *)"Sunnybaer", (u8 *)"baer1234", 0, 20, &status) == 0) // not saved, timeout=20s
      return 0;
#endif

    // Wait the module to got ip address if it works in STA mode
    // (Chinese: 如果模组工作在包含STA的模式下，需要等待模组从所连接的热点/路由器获取到ip地址。因为获取到ip地址，是后面进行套接字通信的前提，因此，这里需要等待，确保模组获取到ip真正连接成功)
    // u8 M8266WIFI_SPI_wait_sta_connecting_to_ap_and_get_ip(char* sta_ip, u8 max_wait_time_in_s)
    if (M8266WIFI_SPI_wait_sta_connecting_to_ap_and_get_ip(sta_ip, 20) == 0) // max wait 20s to get sta ip
    {                                                                        //(Chinese: 最多等待20秒。max_wait_time_in_s可以根据实际情形调整。但这个时间不是实际等待的时间，而是最大等待时间超时上限。这个函数会在获取到ip地址或等待时间到达这里的超时上限时返回)
      return 0;
    }
#if 0 // If you expect to to know the reason of failure by above M8266WIFI_SPI_wait_sta_connecting_to_ap_and_get_ip(), enable below
      // (Chinese: 如果你希望查看联网失败后的原因，你可以在这里使用 #if 1)
    else
    {
      if (M8266WIFI_SPI_Get_STA_Connection_Status(&connection_status, &status) == 0) // connection_status will give the status of last connecting
        return 0;                                                                    // (Chinese: connection_status将返回上次连接热点/路由器的结果状态，协助诊断)
    }
#endif

#if 0 // these below function is just an example on how to use them. You may or may not use them during module initialisation
      // (Chinese: 下面这个函数只是一个例子，来演示如何获取模组连接热点/路由器时的信号强度，你可以在这里使用 #if 1来使用它，也可以使用#if 0来不适用它)
      // u8 M8266WIFI_SPI_STA_Query_Current_SSID_And_RSSI(char* ssid, u8* rssi, u16* status)
    if (M8266WIFI_SPI_STA_Query_Current_SSID_And_RSSI(ssid, &rssi, &status) == 0)
      return 0;
#endif
  } // end to if(  (sta_ap_mode == 1) ....
#endif
  return 1;
}

/***********************************************************************************
 * M8266WIFI_Module_Test                                           *                                                            *
 ***********************************************************************************/
void M8266WIFI_Test(void)
{
  u32 i;

  led_flash(3, 3); // 单片机主板上的LED灯闪烁4次，用于提示代码执行到这里的方便调试。和模组通信无关，非必须，可略。若没有定义宏USE_LED_AND_KEY_FOR_TEST，此处不会被编译进来。

//     Setup Connection and Config connection upon neccessary (Chinese: 创建套接字，以及必要时对套接字的一些配置)      //

// step 0: config tcp windows number (Chinese: 步骤0：如果是TCP类型的套接字，可以配置调整窗口参数）
#if (0 && ((TEST_CONNECTION_TYPE == SOCKET_TYPE_TCP_CLIENT) || (TEST_CONNECTION_TYPE == SOCKET_TYPE_TCP_SERVER))) // If you hope to change TCP Windows, please change '0' to '1' in the #if clause before setup the connection
  //(Chinese: 如果想改变套接字的窗口数，可以将#if语句中的0改成1，这个配置需要在创建套接字之前执行)
  // u8 M8266WIFI_SPI_Config_Tcp_Window_num(u8 link_no, u8 tcp_wnd_num, u16* status)
  if (M8266WIFI_SPI_Config_Tcp_Window_num(link_no, 4, &status) == 0)
  {
    err_8266();
  }
#endif

  // step 1: setup connection (Chinese: 步骤1：创建套接字连接）

  // u8 M8266WIFI_SPI_Setup_Connection(u8 tcp_udp, u16 local_port, char remote_addr, u16 remote_port, u8 link_no, u8 timeout_in_s, u16* status);
  reconnect:  if (M8266WIFI_SPI_Setup_Connection(TEST_CONNECTION_TYPE, TEST_LOCAL_PORT, TEST_REMOTE_ADDR, TEST_REMOTE_PORT, link_no, 20, &status) == 0)
  {
    err_8266();
		goto reconnect;
  }
  else // else: setup connection successfully, we could config it (Chinese: 创建套接字成功，就可以配置套接字）
  {
#if (0 && (TEST_CONNECTION_TYPE == SOCKET_TYPE_UDP)) //// If UDP, then the module could join a multicust group. If you hope to use multicust, Change first '0' to '1'  in the #if clause
                                                     //   (Chinese: 如果这个套接字是UDP，那么可以配置成组播模式。如果需要配置成组播，可以将#if语句中的第一个'0'改成'1')
                                                     // u8 M8266WIFI_SPI_Set_Multicuast_Group(u8 join_not_leave, char multicust_group_ip[15+1], u16* status)
    if (M8266WIFI_SPI_Op_Multicuast_Group(0, "224.6.6.6", &status) == 0)
    {
      err_8266();
    }
    else
#elif (TEST_CONNECTION_TYPE == SOCKET_TYPE_TCP_SERVER) //// If TCP server, then tcp server auto disconnection timeout, and max clients allowed could be set
    //  (Chinese: 如果TCP服务器，那么可以设置这个TCP服务器(因长时间无通信而)断开客户端的超时时间)
#if 1
    // u8 M8266WIFI_SPI_Set_TcpServer_Auto_Discon_Timeout(u8 link_no, u16 timeout_in_s, u16* status)
    if (M8266WIFI_SPI_Set_TcpServer_Auto_Discon_Timeout(link_no, 120, &status) == 0)
    {
      err_8266();
    }
#endif

#if 0
                //u8 M8266WIFI_SPI_Config_Max_Clients_Allowed_To_A_Tcp_Server(u8 server_link_no, u8 max_allowed, u16* status);
                  else if( M8266WIFI_SPI_Config_Max_Clients_Allowed_To_A_Tcp_Server(link_no, 5, &status)==0)
                  {
                      while(1)
                      {
#ifdef USE_LED_AND_KEY_FOR_TEST // led flash in 1Hz when error
                          LED_set(0, 0); LED_set(1, 0); HAL_Delay(1000);
                          LED_set(0, 1); LED_set(1, 1); HAL_Delay(1000);
#endif		 
                      }
                  }
#endif
    else
#endif // endif to -> #elif (TEST_CONNECTION_TYPE == SOCKET_TYPE_TCP_SERVER)

    // Setup Connection successfully (Chinese: 设置套接字链接成功)
    {
#ifdef USE_LED_AND_KEY_FOR_TEST
      // led 1 flash 4 times upon success
      LED_set(1, 0);
      HAL_Delay(50);
      LED_set(1, 1);
      HAL_Delay(50);
      LED_set(1, 0);
      HAL_Delay(50);
      LED_set(1, 1);
      HAL_Delay(50);
      LED_set(1, 0);
      HAL_Delay(50);
      LED_set(1, 1);
      HAL_Delay(50);
      LED_set(1, 0);
      HAL_Delay(50);
      LED_set(1, 1);
      HAL_Delay(50);
#endif
    }
  }

//	            Communication Test (Chinese: WIFI套接字的数据收发通信测试)                                         //
#if (TEST_M8266WIFI_TYPE == 1) // Transmission Test: to test send data to remote constantly // (Chinese: 发送测试，不断地向目标发送数据)
  {

#define TEST_SEND_DATA_SIZE 2920 // 5840 //2048 // 1024   // If using large size of array, ensure system stack is large engough for the array variable。 Or stack over-bound leakage might bring about the mcu into "hardware fault exception"

    u8 snd_data[TEST_SEND_DATA_SIZE]; // (Chinese: 如果使用较大的数组，记得确保有足够大的系统堆栈来容纳这个大数组变量. 否则，单片机程序可能会因为堆栈溢出越界而跳入“hardware fault"系统异常)
    volatile u32 sent = 0;
    volatile u32 total_sent = 0, MBytes = 0;
    u16 batch;

    volatile u8 debug_point;

    for (i = 0; i < TEST_SEND_DATA_SIZE; i++)
      snd_data[i] = i; // Generate an array of data to transmit (Chinese: 生成一个连续数据的数组作为待发送的数据)

    for (batch = 0;; batch++)
    {
      // below used to convenient reception end to differentiate packets when test and evaluate the reliability of transmission. Not necesary.
      // (Chinese: 下面三句话是在改造发送的包，这样前后连续发送的包会不一样，这样在做传输的可靠性分析时，方便接收端分析数据。非必须。)
      snd_data[0] = batch >> 8; // first two bytes using bacth to differentiate the continous packets (Chinese: 包开始两个字节设定为不同的序号，以便区分不同的包)
      snd_data[1] = batch & 0xFF;
      snd_data[TEST_SEND_DATA_SIZE - 2] = 0xFF; // last byte using customised to lable end of a packet for the sake of searching for data verification. Here FF FF as an example
      snd_data[TEST_SEND_DATA_SIZE - 1] = 0xFF; // (Chinese: 包里的最后两个字节设定为一个特殊的字节，以方便接收端搜索包的结尾。这里举例用的是 FF FF。因为产生的发送数据时顺序递增的本身不可能出现FF FF，所以这样改造后收到的FF FF必然代表结尾。也可以用其他的模式匹配。

      if (total_sent > 1024 * 1024) // watch MBytes*1024*1024+total_sent, which is the count of data module sends, compared with the received count at the reception end, to determin the packet loss etc
      {                             // (Chinese: 持续发送一段时间后，观察表达式 MBytes*1024*1024+total_sent 的值，和接收端接收到的数据个数进行比较，可以粗略衡量模组的丢包率。)
        MBytes++;
        total_sent -= 1024 * 1024;
#ifdef USE_LED_AND_KEY_FOR_TEST // Toggle flash the LEDs n the MCU main board every MBytes transmited (Chinese: 每发送1兆字节的数据，主板上的LED灯就交替闪烁一次)
        LED_set(0, MBytes & 0x01);
        LED_set(1, 1 - (MBytes & 0x01));
#endif
      }

#if 1 // using M8266WIFI_SPI_Send_BlockData() to sent block data (Chinese: 调用M8266WIFI_SPI_Send_BlockData() 来发送大块数据
      {
        // u32 M8266WIFI_SPI_Send_BlockData(u8 Data[], u32 Data_len, u16 max_loops, u8 link_no, char* remote_ip, u16 remote_port, u16* status);
        sent = M8266WIFI_SPI_Send_BlockData(snd_data, (u32)TEST_SEND_DATA_SIZE, 5000, link_no, NULL, 0, &status); // !! Kind noted: For those platform compilation by TI/IAR, such MSP430, K60, TMS28335 etc, PLEASE add (u32) to mandatorily convert a const to u32, or, the parameter transmission will be 16-bit and bring about function calling failure
                                                                                                                  // (Chinese) 对于那些TI提供的平台编译器，例如MSP430, K60，TMS28335,等等，注意这里的Data_len参数是32位的，所以，请注意，传递一个不超过2^16的常值的长度参数时，一定要标注其位u32，比如(u32)TEST_SEND_DATA_SIZE，或者 2048UL 否则，可能会出现参数传递错位的情形。这个问题可能只存在于TI或IAR的某些编译器环境下。
        total_sent += sent;

        if ((sent == TEST_SEND_DATA_SIZE) && ((status & 0xFF) == 0x00)) // Send successfully (Chinese: 发送成功)
        {
        }
        else if ((status & 0xFF) == 0x1E) // 0x1E = too many errors ecountered during sending and can not fixed, or transsmission blocked heavily(Chinese: 发送阶段遇到太多的错误或阻塞了，可以考虑加大max_loops)
        {
          debug_point = 1;
          // add some process here (Chinese: 可以在此处加一些处理，比如增加max_loops的值)
        }
        else if (((status & 0xFF) == 0x14)     // 0x14 = connection of link_no not present (Chinese: 该套接字不存在)
                 || ((status & 0xFF) == 0x15)) // 0x15 = connection of link_no closed(Chinese: 该套接字已经关闭或断开)
        {
          debug_point = 2;
          // need to re-establish the socket connection (Chinese: 需要重建建立套接字)
        }
        else if ((status & 0xFF) == 0x18) // 0x18 = TCP server in listening states and no tcp clients have connected. (Chinese: 这个TCP服务器还没有客户端连接着它)
        {
          debug_point = 3;
          M8266HostIf_delay_us(99);
        }
        else
        {
          debug_point = 4;
          M8266HostIf_delay_us(101);
        }
      }
#else // using M8266WIFI_SPI_Send_Data() to sent data packet by packet (Chinese: 调用M8266WIFI_SPI_Send_Data() 来一个一个包的发送数据，实际几乎就是M8266WIFI_SPI_Send_BlockData()的函数实现代码
      {
        u16 tcp_packet_size = 1024;
        u16 loops = 0;
        u16 max_loops = 5000;
        u32 len = TEST_SEND_DATA_SIZE;

        for (sent = 0, loops = 0; (sent < len) && (loops <= max_loops); loops++)
        {
          sent += M8266WIFI_SPI_Send_Data(snd_data + sent, ((len - sent) > tcp_packet_size) ? tcp_packet_size : (len - sent), link_no, &status);
          if (sent >= len)
            break;
          if ((status & 0xFF) == 0x00)
          {
            loops = 0;
          }
          else
          {
            if (((status & 0xFF) == 0x14)     // 0x14 = connection of link_no not present (Chinese: 该套接字不存在)
                || ((status & 0xFF) == 0x15)) // 0x15 = connection of link_no closed(Chinese: 该套接字已经关闭或断开)
            {
              M8266HostIf_delay_us(99);
              // need to re-establish the socket connection (Chinese: 需要重建建立套接字)
            }
            else if ((status & 0xFF) == 0x18) // 0x18 = TCP server in listening states and no tcp clients have connected. (Chinese: 这个TCP服务器还没有客户端连接着它)
            {
              M8266HostIf_delay_us(100);
            }
            else
            {
              M8266HostIf_delay_us(250);
            }
          }
        } // end of for(...

        total_sent += sent;
      }
#endif

    } // end of for(batch=0; ; batch++)
  }

/////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
/////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
#elif (TEST_M8266WIFI_TYPE == 2) // to test constant reception from remote // (Chinese: 接收送测试，不断地接收来自远端节点发送的数据)
  {

#define RECV_DATA_MAX_SIZE 2048 // If using large size of array, ensure system stack is large engough for the array variable。 Or stack over-bound leakage might bring about the mcu into "hardware fault exception"

    u8 RecvData[RECV_DATA_MAX_SIZE]; // (Chinese: 如果使用较大的数组，记得确保有足够大的系统堆栈来容纳这个大数组变量. 否则，单片机程序可能会因为堆栈溢出越界而跳入“hardware fault"系统异常)
    u16 received = 0;
    u32 total_received = 0;
    u32 MBytes = 0;

    for (i = 0; i < RECV_DATA_MAX_SIZE; i++)
      RecvData[i] = 0xFF - i;
    M8266WIFI_SPI_Send_Data(RecvData, 1024, link_no, &status); // just send to check the ip address on TCP tester. not necessary
                                                               // (Chinese: 向远端节点发送一个包，以便在远端查看模组的IP地址和端口，非必须)
    while (1)
    {
      if (M8266WIFI_SPI_Has_DataReceived())
      {
        // u16 M8266WIFI_SPI_RecvData(u8 data[], u16 max_len, uint16_t max_wait_in_ms, u8* link_no, u16* status);
        received = M8266WIFI_SPI_RecvData(RecvData, RECV_DATA_MAX_SIZE, 5 * 1000, &link_no, &status);

        if ((status & 0xFF) != 0)
        {
          if ((status & 0xFF) == 0x22) // 0x22 = Module buffer has no data received
          {
            M8266HostIf_delay_us(250);
            // HAL_Delay(1);
          }
          else if ((status & 0xFF) == 0x23) // 0x23  = the packet had not been received completed in the last call of M8266WIFI_SPI_RecvData() and has continued to be received in this call of M8266WIFI_SPI_RecvData(). do some work here if necessary
          {                                 // (Chinese: 上次调用接收函数M8266WIFI_SPI_RecvData()时，并未收完整上次那个包，于是这次调用继续接受之前的包。可以在这里做一些工作，比如将一次接收缓冲区和做大长度上限加大。
          }
          else if ((status & 0xFF) == 0x24) // 0x24  = the packet is large in size than max_len specified and received only the max_len
          {                                 // (Chinese: 模组所接收到而正在被读取的这个包的长度，超过了这里的max_len参数所指定的长度。通常是因为远端阵发发送或路由器等阻塞时出现了大面积粘包导致到达模块的包过长，
                                            //           或者远端实际发送的就是一个长包，其长度超过了这里所指定的最大长度上限。如果是前者的原因，建议暂停远端TCP通信一段时间。如果是后者，建议加大max_len的数值或者
                                            //           不做任何处理，不做处理时，单片机侧这边接收到的长包会被拆成多个小包需要自行再次破解。
                                            //           必要时可以做相应的处理。
          }
          else
          {
            // do some work here if necessary
            // (Chinese: 其他异常，必要时可以做一些处理。
          }
        }
#define TEST_RECV_UNIT (1024 * 1024)
        total_received += received;
        if (total_received >= (TEST_RECV_UNIT))
        {
          total_received = total_received % (TEST_RECV_UNIT);
          MBytes++;
#ifdef USE_LED_AND_KEY_FOR_TEST // Toggle flash the LEDs n the MCU main board every MBytes received (Chinese: 每接收1兆字节的数据，主板上的LED灯就交替闪烁一次)
          LED_set(0, MBytes & 0x01);
          LED_set(1, 1 - (MBytes & 0x01));
#endif
        }
      } // end of if(M8266WIFI_SPI_Has_DataReceived())
    }   // end of while(1)
  }

/////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
/////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
#elif (TEST_M8266WIFI_TYPE == 3) // Echo test: to receive data from remote and then echo back to remote (Chinese: 收发测试，模组将接收到的数据立刻返回给发送方)
  {

    for (i = 0; i < RECV_DATA_MAX_SIZE; i++)
      RecvData[i] = i;

//    sent = M8266WIFI_SPI_Send_Data(RecvData, 1024, link_no, &status);//发送测试

    //    while (1)
    //    {
    //      if (M8266WIFI_SPI_Has_DataReceived()) // if received data (Chinese: 如果接收到数据)
    //      {
    //        // Receive the data (Chinese: 单片机接收数据)
    //        // u16 M8266WIFI_SPI_RecvData(u8 data[], u16 max_len, uint16_t max_wait_in_ms, u8* link_no, u16* status);
    //        received = M8266WIFI_SPI_RecvData(RecvData, RECV_DATA_MAX_SIZE, 5 * 1000, &link_no, &status);

    //        if (received != 0) // if received data length is not 0  (Chinese: 如果单片机的确接收到了数据，即长度不等于0)
    //        {
    //             u16 tcp_packet_size = 1024;
    //             u16 loops = 0;
    //             u16 max_loops = 5000;
    //          u32 len = received;

    //          for (sent = 0, loops = 0; (sent < len) && (loops <= max_loops); loops++)
    //          {
    //            sent += M8266WIFI_SPI_Send_Data(RecvData + sent, ((len - sent) > tcp_packet_size) ? tcp_packet_size : (len - sent), link_no, &status);
    //            if (sent >= len)
    //              break;
    //            if ((status & 0xFF) == 0x00)
    //            {
    //              loops = 0;
    //            }
    //            else
    //            {
    //              if (((status & 0xFF) == 0x14)     // 0x14 = connection of link_no not present (Chinese: 该套接字不存在)
    //                  || ((status & 0xFF) == 0x15)) // 0x15 = connection of link_no closed(Chinese: 该套接字已经关闭或断开)
    //              {
    //                M8266HostIf_delay_us(99);
    //                // need to re-establish the socket connection (Chinese: 需要重建建立套接字)
    //              }
    //              else if ((status & 0xFF) == 0x18) // 0x18 = TCP server in listening states and no tcp clients have connected. (Chinese: 这个TCP服务器还没有客户端连接着它)
    //              {
    //                M8266HostIf_delay_us(100);
    //              }
    //              else
    //              {
    //                M8266HostIf_delay_us(250);
    //              }
    //            }
    //          } // end of for(sent=0, loops=0; .....
    //        }   // end of if(received!=0)
    //      }     // end of if(M8266WIFI_SPI_Has_DataReceived())
    //    }       // end of while(1)
  }

/////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
/////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
#elif (TEST_M8266WIFI_TYPE == 4) //  multi-clients transimission test: TCP Server or UDP receive data from multilple sender and echo back the data to the coresponding source
                                 // (Chinese: 多客户端收发测试，模组作为TCP服务器或UDP，接收来自多个远端节点的数据，并将接收到的数据立刻发送回给对应的发送方)
#if ((TEST_CONNECTION_TYPE != 0) && (TEST_CONNECTION_TYPE != 2))
#error multiple clients test could be supported only when socket connection is UDP or TCP Server
#endif

  {

#define RECV_DATA_MAX_SIZE 2048 // If using large size of array, ensure system stack is large engough for the array variable。 Or stack over-bound leakage might bring about the mcu into "hardware fault exception"

    u8 RecvData[RECV_DATA_MAX_SIZE]; // (Chinese: 如果使用较大的数组，记得确保有足够大的系统堆栈来容纳这个大数组变量. 否则，单片机程序可能会因为堆栈溢出越界而跳入“hardware fault"系统异常)
    u16 received = 0;
    u16 sent;
    u8 remote_ip[4];
    u16 remote_port;

    for (i = 0; i < RECV_DATA_MAX_SIZE; i++)
      RecvData[i] = i;

    while (1)
    {
      if (M8266WIFI_SPI_Has_DataReceived()) // if received data (Chinese: 如果接收到数据)
      {
        // Receive the data using M8266WIFI_SPI_RecvData_ex() which could return the address and port of the sender
        //(Chinese: 单片机使用函数M8266WIFI_SPI_RecvData_ex()来接收数据，这个函数可以返回发送方的地址和端口)

        // u16 M8266WIFI_SPI_RecvData_ex(u8 Data[], u16 max_len, uint16_t max_wait_in_ms, u8* link_no, u8 remote_ip[4], u16* remote_port, u16* status)
        received = M8266WIFI_SPI_RecvData_ex(RecvData, RECV_DATA_MAX_SIZE, 5 * 1000, &link_no, remote_ip, &remote_port, &status);

        if (received != 0) // if received data length is not 0  (Chinese: 如果单片机的确接收到了数据，即长度不等于0)
        {
          // format the address in hex to ascii(Chinese: 将16进制格式的ip地址转化字符串形式的ip地址，因为下面的函数传递的是字符串形似的地址参数)
          char dest_addr[15 + 1] = {0};
          sprintf(dest_addr, "%d.%d.%d.%d", remote_ip[0], remote_ip[1], remote_ip[2], remote_ip[3]);

          u16 tcp_packet_size = 1024;
          u16 loops = 0;
          u16 max_loops = 5000;
          u32 len = received;

          for (sent = 0, loops = 0; (sent < len) && (loops <= max_loops); loops++)
          {

#if (TEST_CONNECTION_TYPE == SOCKET_TYPE_UDP) // if UDP, (Chinese: UDP实现向多个对等的UDP服务发送数据
                                              // If UDP, Send the data using M8266WIFI_SPI_Send_Udp_Data() which could send the data to the specified UDP peer
                                              //(Chinese: 如果是UDP，单片机使用函数M8266WIFI_SPI_Send_Udp_Data()来发送数据，这个函数可以传递目标地址和端口，也就是可以指定发送到哪个对等的UDP服务节点)
            // u16 M8266WIFI_SPI_Send_Udp_Data(u8 Data[], u16 Data_len, u8 link_no, char* udp_dest_addr, u16 udp_dest_port, u16* status)
            sent += M8266WIFI_SPI_Send_Udp_Data(RecvData + sent, ((len - sent) > tcp_packet_size) ? tcp_packet_size : (len - sent), link_no, dest_addr, remote_port, &status);

            if (sent >= len)
              break;

            if ((status & 0xFF) == 0x00)
            {
              loops = 0;
            }
            else
            {
              if ((status & 0xFF) == 0x14) // 0x14 = connection of link_no not present (Chinese: 该套接字不存在)
              {
                M8266HostIf_delay_us(99);
                // need to re-establish the socket connection (Chinese: 需要重建建立套接字)
              }
              else
              {
                M8266HostIf_delay_us(250);
              }
            }
#elif (TEST_CONNECTION_TYPE == SOCKET_TYPE_TCP_SERVER)
            // If tcp server, Send the data using M8266WIFI_SPI_Send_Data_to_TcpClient() which could send the data to the specified client
            //(Chinese: 如果是TCP服务器，单片机使用函数M8266WIFI_SPI_Send_Data_to_TcpClient()来发送数据，这个函数可以传递目标地址和端口，也就是可以指定发送到哪个客户端)
            // u16 M8266WIFI_SPI_Send_Data_to_TcpClient(u8 Data[], u16 Data_len, u8 server_link_no, char* tcp_client_dest_addr, u16 tcp_client_dest_port, u16* status)
            sent += M8266WIFI_SPI_Send_Data_to_TcpClient(RecvData + sent, ((len - sent) > tcp_packet_size) ? tcp_packet_size : (len - sent), link_no, dest_addr, remote_port, &status);
            if (sent >= len)
              break;
            if ((status & 0xFF) == 0x00)
            {
              loops = 0;
            }
            else
            {
              if (((status & 0xFF) == 0x14)     // 0x14 = connection of link_no not present (Chinese: 该套接字不存在)
                  || ((status & 0xFF) == 0x15)) // 0x15 = connection of link_no closed(Chinese: 该套接字已经关闭或断开)
              {
                M8266HostIf_delay_us(99);
                // need to re-establish the socket connection (Chinese: 需要重建建立套接字)
              }
              else if ((status & 0xFF) == 0x18) // 0x18 = TCP server in listening states and no tcp clients have connected. (Chinese: 这个TCP服务器还没有客户端连接着它)
              {
                M8266HostIf_delay_us(100);
              }
              else
              {
                M8266HostIf_delay_us(250);
              }
            }
#endif

          } // end of for(sent=0, loops=0; ...
        }   // end of if(received!=0)
      }     // end of if(M8266WIFI_SPI_Has_DataReceived())
    }       // end of while(1)
  }

/////////////////////////////////////////////////////////////////////////////
/////////////////////////////////////////////////////////////////////////////
#else
#error NOT Supported Test Type! should be 1~4!
#endif

} // end of M8266WIFI_Test

void start_8266(void)
{
  u8 success = 0;
  // 0.微妙延时初始化
  delay_Init(); //  Initialise the paramters for delay_us() which is called by M8266WIFI Driver(Chinese: 初始化例程里延时函数delay_us()所需要的一些参数，而M8266WIFI驱动库会用到delay_us())

  // 1.调用M8266HostIf_Init()，来初始化单片机的SPI主机接口;包括对SPI寄存器和nRESET/nCS GPIO管脚的GPIO初始化
  //   Call M8266HostIf_Init() to initialize the MCU Host Interface
  //      - including SPI and nREST/nCS GPIO pins
  M8266HostIf_Init();
  led_flash(3, 1);

  // 2.调用M8266WIFI_Module_Init_Via_SPI()，来通过SPI接口初始化WIFI模组；包括对模组复位、接口选定、必要时连接路由器，等等操作
  //  2. Call M8266WIFI_Module_Init_Via_SPI() to initialize the wifi module via SPI Interface
  //       (Chinese: 调用M8266WIFI_Module_Init_Via_SPI()，来通过SPI接口初始化WIFI模组)
  //     - Including: Module reset, module select, module connecting wifi if required, and etc
  //       (Chinese: 包括对模组复位、接口选定、必要时连接路由器，等等操作)
  //     - Recommend to step into to see the detail of this function
  //       (Chinese: 初次熟悉时建议单步调试进入这个函数，查看其具体功能实现)
  /////////////////////////////////////////////////////////////////////////////////////////////////////
  success = M8266WIFI_Module_Init_Via_SPI();
  if (success)
  {
  }
  else
    err_8266();

  // 3.调用M8266WIFI_Test()，来创建套接字链接，进行高速收发测试
  //  3. Call M8266WIFI_Test() to setup socket connection and tansmit/receive data test in a high-speed manner
  //     (Chinese: 调用M8266WIFI_Test()，来创建套接字链接，进行高速收发测试)
  /////////////////////////////////////////////////////////////////////////////////////////////////////
  M8266WIFI_Test();
}
u8 hlly[] = "哈利路亚";
static char sprint_buf[2048];

int myPrintf(const char *fmt, ...)
{
  va_list args;
  int n;
	u8 success = 0;
  // 第一个可变形参指针
  va_start(args, fmt);
  // 根据字符串fmt，将对应形参转换为字符串，并组合成新的字符串存储在sprint_buf[]缓存中，返回字符个数。
  n = vsprintf(sprint_buf, fmt, args);
  // c标准要求在同一个函数中va_start 和va_end 要配对的出现。
  va_end(args);
  // 调用相关驱动接口,将将sprintf_buf中的内容输出n个字节到设备，
  // 此处可以是串口、控制台、Telnet等,在嵌入式开发中可以灵活挂接
  M8266WIFI_SPI_Send_Data((u8 *)sprint_buf, n, link_no, &status);
	check_sendSuccess();
  return n;
}
u16 RX_TimeOut;
u32 appLen;
u8 buf_appLen[4];
void received_8266(void)
{
	if(flag_update_program)
	{
		if(RX_TimeOut > 0)
		{
			if(--RX_TimeOut == 0)
			{				
				debug("接收超时;");
				buf_appLen[0]=0xff;//0xff代表接收超时错误			
				spi_send(0000,buf_appLen,4);
				packLen=0;//当前包接收计数清空
				file_pack_num=0;//第几包计数清空
				appLen=0;//接收固件长度计数清空
				flag_update_program=0;//退出接收固件模式
			}
		}
	}
}
void task_8266(void)
{
  // 打印连接情况
  //	u8 connection_type,connection_state1,remote_ip;
  //	u16 local_port,remote_port,status;
  //	if(M8266WIFI_SPI_Query_Connection(link_no,&connection_type,&connection_state1,&local_port,&remote_ip,&remote_port,&status)==0)
  //	{
  //		debug("查询失败;");
  //	}
  //	else debug("connection_type%d,connection_state%d,remote_ip%x,remote_port%d;",connection_type,connection_state1,remote_ip,remote_port);

  if (M8266WIFI_SPI_Has_DataReceived()) // if received data (Chinese: 如果接收到数据)
  {
    // Receive the data (Chinese: 单片机接收数据)
    // u16 M8266WIFI_SPI_RecvData(u8 data[], u16 max_len, uint16_t max_wait_in_ms, u8* link_no, u16* status);
    received = M8266WIFI_SPI_RecvData(RecvData, RECV_DATA_MAX_SIZE, 5 * 1000, &link_no, &status);

    if (received != 0) // if received data length is not 0  (Chinese: 如果单片机的确接收到了数据，即长度不等于0)
    {
      u16 tcp_packet_size = 1024;
      u16 loops = 0;
      u16 max_loops = 5000;
      u32 len = received;
			
      //				M8266WIFI_SPI_Send_Data(hlly,sizeof(hlly), link_no, &status);

#ifdef TOUCHUAN_OFF
      if (flag_update_program)
      {
				RX_TimeOut=3000;
				if(packLen+len>=size_file_pack)
				{
					memcpy(file_pack+packLen,RecvData,size_file_pack-packLen);//存满
					iap_write_appbin(FLASH_APP1_ADDR+0x7800*file_pack_num,file_pack,size_file_pack);//存到flash
					packLen=packLen+len-size_file_pack;
					memcpy(file_pack,RecvData+len-packLen,packLen);//剩下的存到数组,注意recv偏移地址为len-新applen=已存len
					file_pack_num+=1;
				}
				else
				{
					memcpy(file_pack+packLen,RecvData,len);
					packLen+=len;
				}
				appLen+=len;
				buf_appLen[0]=appLen>>24;
				buf_appLen[1]=appLen>>16&0xff;
				buf_appLen[2]=appLen>>8&0xff;
				buf_appLen[3]=appLen&0xff;
				
				spi_send(0000,buf_appLen,4);
				
				if(appLen==fileLen)
				{
					debug("接收完成,长度为%d;",appLen);
					appLen=0;//接收固件长度计数清空
					flag_update_program=0;//退出接收固件模式
					iap_write_appbin(FLASH_APP1_ADDR+0x7800*file_pack_num,file_pack,packLen);//最后一包数据存到flash
					packLen=0;//当前包接收计数清空
					file_pack_num=0;//第几包计数清空
					debug("保存完成;");
				}
//        flag_update_program = 0;
//        packLen = len;
//        debug("hlly;");
//        debug("代码长度:%dBytes;", packLen);
//        debug("用户程序接收完成!;");
      }
      else if (len < 256)
      {
        uartData_depart(RecvData, len);
      }
      //				myPrintf("哈利路亚%s%d%f","hlly",777,0.3f);
#else
			

      // 收到数据原样转发
      for (sent = 0, loops = 0; (sent < len) && (loops <= max_loops); loops++)
      {
//        sent += M8266WIFI_SPI_Send_Data(RecvData + sent, ((len - sent) > tcp_packet_size) ? tcp_packet_size : (len - sent), link_no, &status);
//        if (sent >= len)
          break;
        if ((status & 0xFF) == 0x00)
        {
          loops = 0;
        }
        else
        {
          if (((status & 0xFF) == 0x14)     // 0x14 = connection of link_no not present (Chinese: 该套接字不存在)
              || ((status & 0xFF) == 0x15)) // 0x15 = connection of link_no closed(Chinese: 该套接字已经关闭或断开)
          {
            M8266HostIf_delay_us(99);
            // need to re-establish the socket connection (Chinese: 需要重建建立套接字)
          }
          else if ((status & 0xFF) == 0x18) // 0x18 = TCP server in listening states and no tcp clients have connected. (Chinese: 这个TCP服务器还没有客户端连接着它)
          {
            M8266HostIf_delay_us(100);
          }
          else
          {
            M8266HostIf_delay_us(250);
          }
        }
      } // end of for(sent=0, loops=0; .....
#endif
    } // end of if(received!=0)
  }   // end of if(M8266WIFI_SPI_Has_DataReceived())
}
void spi_send(u16 cmdID, u8 *TXmessage, u8 len)
{
	u8 success = 0;
  u8 exLen = 8; // 上位机协议固定8个字节，有效数据len个字节
  u8 tmp_sum;
  u8 *tempdata = (u8 *)malloc(sizeof(u8) * (len + exLen));
  uint8_t buf_toPC[6] = {0x42, 0x44, 0x00, 0x00, 0x42, 0x09};
  memcpy(tempdata, buf_toPC, 6); // 打包帧头和指令
  tempdata[3] = len + exLen;
  tempdata[len + exLen - 1] = 0xda; // 打包数据长度和帧尾
  tempdata[4] = cmdID >> 8 & 0xff;
  tempdata[5] = cmdID & 0xff;
  memcpy(&tempdata[6], TXmessage, len); // 打包数据
  tmp_sum = SumCheck(tempdata, len + exLen - 2);
  //	if(tmp_sum==0x3B)tmp_sum=0x3C;//校验和刚好为分隔符“;”
  tempdata[len + exLen - 2] = tmp_sum; // 打包校验和

  for (int i = 0; i < (len + exLen); i++)
  {
    if (tempdata[i] == 0x3B)
    {
      exLen += 1; // 如果发现分隔符“;”，则需要多增加一个字节
      //			debug("分隔符个数为%d;",exLen-8);
    }
    //		debug("字符%d为%x;",i,tempdata[i]);
  }
  if (exLen > 8)
  {
    //		debug("%d",exLen);
    u8 *tempdata1 = (u8 *)malloc(sizeof(u8) * (len + exLen));
    memcpy(tempdata1, tempdata, len + 8);
    for (int i = 0; i < (len + exLen); i++)
    {
      //			debug("%x",tempdata[i]);
      if (tempdata1[i] == 0x3B)
      {
        //				debug("为%x;",tempdata[i]);
        //				for (int j= 0; j < (len+exLen-i-1); j++)
        //				{
        //					tempdata1[i+j+1]=tempdata1[i+j];//往后移动一位
        //				}
        memcpy(&tempdata1[i + 1], &tempdata1[i], len + exLen - i - 1);

        tempdata1[i] = 0x5C;
        //				debug("为%x;",tempdata[i]);
        i++;
      }
    }
    M8266WIFI_SPI_Send_Data(tempdata1, len + exLen, link_no, &status);
//		check_sendSuccess();
    free(tempdata1);
    free(tempdata);
  }
  else
  {
    M8266WIFI_SPI_Send_Data(tempdata, len + exLen, link_no, &status);
//		check_sendSuccess();
    free(tempdata);
  }
}
u8 device_number[]={0x31,0x50,0x00,0x00,0x01};
void heartBeat(void)
{
	spi_send(0x3001,device_number,5);
}